package learn;

import java.util.Scanner;

/**
 * 多线程操作
 */
class myThread extends Thread{

    public void run() {//多线程会运行这里的方法
        while (true) {
            System.out.println("super.run();");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

}
class myTreadB implements Runnable{
    @Override
    public void run() {
        while (true) {
            System.out.println("run()");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("don't care");
//                throw new RuntimeException(e);
            }
        }
    }
}

class add {
    static int count = 0;

    public static void add1() {
        synchronized (add.class) {//也可以在这里上锁，当多个线程调用了同一个方法
            //也能起到锁对应的作用
            count++;
        }
    }

    public synchronized static void add() {//也可以这样写
        count++;
    }
    public static int getAdd() {
        return count;
    }
}
public class mainRun {

    //将该变量使用volatile修饰，这样就会强制每次使用该变量都在内存中读取也就是只能读到修改后的变量 就不会存在内存可见性的问题
    //保证有序性，禁止指令重排序，编译时jvm会遵循内存屏障的约束，运行是靠内存屏障来组织指令顺序执行
    volatile static int count = 0;
    static Object locker = new Object();
    static Object locker1 = new Object();
    static Object locker2 = new Object();

    /**
     * wait notify java种规定了这俩都必须要在synchronized中使用且对应的锁必须额wait notify使用的锁时一致的
     * 若不使用就会抛出 IllegalMonitorStateException 非法的锁状态异常
     * wait 是使运行到wait线程会进入阻塞等待状态，并会解开这个线程使用的锁，若有其他线程使用相同的锁，那么他就可以使用这把锁
     * 在调用notify唤醒wait过后他又会解除阻塞等待状态并将对应的代码块加锁
     * 等到使用同一把锁的对象调用notify方法才会继续向下执行
     * 如果有多个线程使用同一吧锁的话，谁拿到这把锁是不确定的，操作系统的调度是不确定的（随机调度）
     * 在使用notify是还要确保先要有线程进入wait状态，不然的话你调用notify并不会起作用，也不会在后来
     * 运行到wait的是后直接解除阻塞等待
     */
    public static void main10(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("locker 前");
            synchronized (locker) {
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("locker 后");
        });
        Thread t3 = new Thread(() -> {
            System.out.println("locker t3 前");
            synchronized (locker) {
                try {
                    System.out.println("t3 获得锁");
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("locker t3 后");
        });
        Thread t2 = new Thread(() -> {
            Scanner in = new Scanner(System.in);
            System.out.println("调用线程t1");
           for(int i = 0;i < 2;i++){
                in.next();
                synchronized (locker) {
                    locker.notify();
                }
            }
        });
        t2.start();
        Thread.sleep(5000);//提前调用notify并不能直接跳过阻塞状态
        t1.start();//t1和t3wait的是同一吧锁
        t3.start();

    }

    /**
     * 内存可见性
     * 在t2中跟该了count的值，结果t1线程并没有按找预期结果退出，而count又确实改为了1 那么为什么t1没有退出呢
     * 因为在最终的运行在jvm上的代码是经过了编译器的优化的，在t1中while循环进行的非常快，而编译器在看这个比较
     * 花费的时间大部分都是在从内存中去到count放入寄存器中比较，而比较时锁花费的时间和去改值花费的时间那查了
     * 几个数量级别，且发现比较的都是一个数，于是就将从内存中取该值优化为了从寄存器中去该值让后去比较，然后就发生了
     * count改变了，但是t1的while循环并没有退出
     * 这样在t2修改了变量，但是在t1上看不到修改后的值，那么这就是内存可见性问题
     * 也就是一个线程再使用变量时的到的不是该变量最新跟新的值
     */

    public static void main9(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
        while (count == 0){}
            /*
            while (count == 0) {
                try {
                    Thread.sleep(1);//如果想要取消这个优化，可以加上一个sleep
//                    这样发现优化后改变的时间对于sleep休息的时间来说，就是九牛一毛，干脆就不优化了
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }*/
            System.out.println("t1 exis");
        });

        Thread t2 = new Thread(() -> {
            Scanner in = new Scanner(System.in);
            System.out.println("输入一个数字");
            count = in.nextInt();
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("main out");
    }



    /**
     * 死锁
     * 形成条件
     * 锁是互斥的，一个线程拿到锁之后，另一个线程要拿就必须进入阻塞等待状态
     * 锁是不可抢夺的 一个线程拿到锁之后，另一个线程要的到锁必须进入阻塞等待中，而不能直接抢过来使用
     * 请求和等待，在线程得到锁1后不解锁的情况下尝试获得锁2
     * 循环等待，在多个线程之中锁的循环等待过程中，构成了循环
     * 解决死锁
     * 约定的到所得顺序，如果不满住的话就进入阻塞等待状态
     */
    public static void main8(String[] args) {
        //死锁
        Thread t1 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker2) {
                    //这里要使用locker2但是要等t2的locker2结束，但locker2结束又要等t1的locker1结束
                    //但t1的locker1结束又要等t2的locker2结束，这样死循环了就形成了死锁
                    System.out.println("先1 后 2");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker1) {
                    System.out.println("先2 后1");
                }
            }
        });
        t1.start();
        t2.start();
        System.out.println("main end");
    }


    /**
     * synchronized(){} 该代码块实现加锁和解锁
     */
    // 将当前线程锁死在cpu上，禁止这个线程被调走，同时阻塞使用同一个锁的线程运行

    public static void main7(String[] args) throws InterruptedException {

//        learn.add a = new learn.add();
//        System.out.println(a);
//        StringBuffer 方法中使用了synchronized 所以时线程安全的

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                // 可以使用任何类为锁 只要指向的是同一个类，那么就是同一把锁
                synchronized (locker) {//加锁
                    //这就意味着当对count进行加加时，会使用locker这个类作为
                    //锁，其他和他使用同一吧锁的代码块就会进入阻塞等待状态，必须要等到他运行完解锁和才可以正常运行
                    //当多个相同锁嵌套时，理论上会导致线程死锁，java为了解决这种死锁的问题，就引入了可重入的概念
                    //当加上锁里面的代码块中加上了相同的锁，那么就可以直接进入改代码块而不会被阻塞
                    //也就相当于这些相同锁没有其作用，只有当最开始的那把锁才会起作用
                    //这样的实现：创建一个计数器，当碰到一个相同的锁的{就++ 哦恩岛相同锁的}就--
                    // 最终当count==0时就是解锁的时候
                    synchronized (locker) {
//                    count++;
                    //
                        add.add();
                    }
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
//                synchronized (t1) {
//                    count++;
                    add.add();
//                }
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("coutn = " + add.getAdd());
    }
    public static void main6(String[] args) throws InterruptedException {
        //若像要的扽一个线程指向完才执行下一个，可以使用join
        Thread t = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread end");
        });
        t.start();
//        t.join();//这样就要等thread执行完才执行接下来的语句
        //可以理解为将t这个线程加入到这个主线程中执行，等t线程执行完了，才会继续向下执行
        t.join(2000);//等2s然后继续线下执行
        System.out.println("main end");
    }

    static boolean isRun = true;

    public static void main5(String[] args) throws InterruptedException {
        //类里面的变量就不可以放入重写的这个类中，涉及到变量捕获
        boolean a = false;//因为可能在另外线程中使用这个变量是，发现变量已经销毁了
        //因为在main函数中，当main函数运行完了之后，他的生命周期就结束了，这时线程中使用的就是错误的变量
        //在java中的解决方法就是吧变量拷贝一份，然后自己用，那么在外面他是否被销毁都对线程内没影响了
        //变量捕获后会改值不允许改变，因为如果改变的话，因为线程里面的只是他的拷贝,他这俩个值的生命周期都不相同了，
        // 而如果里面改了，而外面的值不会改变，这样就会导致更多的错误，所以就不允许改变了，不改变值的话还是能跑的
        Thread t = new Thread(() -> {
            while (a) {//内部类使用用外部类的值，这个不涉及变量捕获
//                a = true;
                System.out.println("isRun");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
//                a = false;
            }
            System.out.println("run exit");
        },"后台线程111");
//        a = true;
//        t.setDaemon(true);//后台线程，如果自己要结束运行也可以，也就是run运行完了就完了
        //如果前台(这里就是main)运行完了，那么对应的后台线程也要结束运行

        t.start();

        for (int i = 0; i < 50; i++) {
            System.out.println("main");
            Thread.sleep(1000);
        }
        isRun = false;
        System.out.println("exit");

//        while (false) {
//            System.out.println("stop");
//            Thread.sleep(10000);
//        }
    }

    /**
     * run和start的区别
     * run的作用是描述这个线程具体要执行的逻辑
     * start是真正的去申请一个系统线程
     * 调用start会创建一个新的线程并为他分配资源，在线程创建好了会异步的执行run方法中的逻辑,在run执行完后线程就会进入销毁阶段
     * 调用run方法只是单单调用这个方法，会同步的执行run方法中的内容
     * 调用start会导致并发执行，调用run方法则是顺序的执行
     * 异步 当A调用了B后， A就继续去执行他自己的逻辑 B也开始执行自己的逻辑
     * 同步 当A调用了B后，A会等到B把他的逻辑执行完后才会执行自己的逻辑
     */
    public static void main4(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("ss");
            }
        };

        //lambda
        Thread t2 = new Thread(() -> {//都是重写run方法
            System.out.println("pirnt");
        });

        //new runnable
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable");
            }
        });
    }

    /**
     *  线程不能重复的start是因为当线程结束后会被标记为TERMINATED，然后线程所申请的系统资源将会回收
     *  如果要重新启动这个线程的话，那么线程的资源管理会变的很复杂，并且线程通常在设计是一次性的
     */
    public static void main2(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (!Thread.interrupted()) {
                System.out.println("t1 thread");
                try {
                    Thread.sleep(1000);
//                    System.out.print("t1 ");
                } catch (InterruptedException e) {
//                    throw new RuntimeException(e);
                    //若什么都不处理的话，那么会变成死循环 虽然外面打断了进程，但是并不能通过
                    //Thread.interrupted()这个方法来结束循环，因为在sleep在被interrupt提前唤醒并
                    //向上抛出了InterruptedException异常后，吧Thread.interrupted()这个方法的状态给重置了，又变为未打断的状态了
                    //这样的话我们可以执行决定在打断后，该进程是要直接抛异常退出，或者是执行其他的代码
                    //亦或是不管该打断，继续执行本来的代码
                }
            }
        });
        t1.start();
//        t1.join();
        for (int i = 0; i < 5; i++) {
            Thread.sleep(100);
        }
        t1.interrupt();//打断t1
        System.out.println("main exit");

//        Thread t2 = new Thread(() -> {
//            while (Thread.interrupted()) {
//                System.out.print("t2 ");
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    throw new RuntimeException(e);
//                }
//            }
//        });

//        t2.start();
    }

    public static void main1(String[] args) {
        //也可以通过传入一个实现Runnable接口的来实现多线程
        Runnable r = new myTreadB();
        Thread t = new Thread(r);
        //查看是否被打断
//        System.out.println(t.isInterrupted());
//        System.out.println(t.getState();//获取线程当前状态
        System.out.println(t.isAlive());//看该线程是否存在
        t.setDaemon(true);//变成后台线程
        t.start();
        for (int i = 0; i < 100; i++) {

            System.out.println(t.isInterrupted());
//            System.out.println(t.getState());
            System.out.println(t.isAlive());
            System.out.println("ee");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            t.interrupt();//当打断了线程t之后，不止isInterrupted()的方法的返回值会改变
            //并且会向上声明一个异常InterruptedException 然后被try catch 捕获处理最终
            //抛出了这个异常，new RuntimeException(e)程序中断
        }

    }


    public static void main0(String[] args) throws InterruptedException {
        myThread m = new myThread();

        m.start();//开始一个线程
//        m.run();
        while (true) {
            System.out.println("learn.mainRun");
            Thread.sleep(1000);
        }

    }



}
